package com.sunny.feign.custom;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;

import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.stream.Stream;

/**
 * 自定义feign
 *
 * @author Sunny
 * @date 2020/2/24
 */
public class CustomFeignClientsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware, EnvironmentAware {
    private BeanFactory beanFactory;
    private Environment environment;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableCustomFeignClients.class.getName());
        Class<?>[] clients = (Class<?>[]) attrs.get("clients");

        //过滤出接口且被CustomFeignClient注解标注的
        Stream.of(clients)
                .filter(Class::isInterface)
                .filter(interfaceClass -> AnnotationUtils.findAnnotation(interfaceClass, CustomFeignClient.class) != null)
                .forEach(client -> {
                    CustomFeignClient customFeignClient = AnnotationUtils.findAnnotation(client, CustomFeignClient.class);
                    //表达式获取服务名
                    String serviceName = environment.resolvePlaceholders(customFeignClient.name());
                    String beanName = "Proxy$" + serviceName;

                    //创建代理类
                    Object proxy = Proxy.newProxyInstance(metadata.getClass().getClassLoader(), new Class[]{client}, new RequestMappingInvocationHandler(serviceName, this.beanFactory));

                    //注测Bean方式一
//                    if (registry instanceof SingletonBeanRegistry) {
//                        SingletonBeanRegistry singletonBeanRegistry = (SingletonBeanRegistry)registry;
//                        singletonBeanRegistry.registerSingleton(beanName, proxy);
//                    }

                    //注测Bean方式二
                    registryBean(beanName, proxy, client, registry);
                });

    }

    /**
     * 注册Bean
     *
     * @param beanName bean名称
     * @param proxy 代理类
     * @param clientClass 被代理类
     * @param registry BeanDefinition
     */
    private void registryBean(String beanName, Object proxy, Class<?> clientClass, BeanDefinitionRegistry registry) {
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(CustomFeignClassFactoryBean.class);
        beanDefinitionBuilder.addConstructorArgValue(proxy);
        beanDefinitionBuilder.addConstructorArgValue(clientClass);

        AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
        //注册bean定义
        registry.registerBeanDefinition(beanName, beanDefinition);
    }

    private static class CustomFeignClassFactoryBean implements FactoryBean<Object> {
        private Object proxy;
        private Class<?> targetClass;

        public CustomFeignClassFactoryBean(Object proxy, Class<?> targetClass) {
            this.proxy = proxy;
            this.targetClass = targetClass;
        }

        @Override
        public Object getObject() throws Exception {
            return proxy;
        }

        @Override
        public Class<?> getObjectType() {
            return targetClass;
        }
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }
}
